package com.wchen.gdoc.generate.config;

import com.wchen.gdoc.generate.mapping.resolver.*;
import com.wchen.gdoc.generate.producer.DefaultParamValueProducer;
import com.wchen.gdoc.generate.producer.ParamValueProducer;
import com.wchen.gdoc.generate.processor.DataProcessor;
import com.wchen.gdoc.generate.processor.DefaultDataProcessor;
import com.wchen.gdoc.generate.resource.ResourceLoader;
import com.wchen.gdoc.generate.resource.analysis.*;
import com.wchen.gdoc.generate.resource.analysis.BaseTypeDocAnalysis;
import com.wchen.gdoc.generate.resource.analysis.DocTypeAnalysis;
import com.wchen.gdoc.generate.resource.loader.DefaultClassLoader;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.*;

/**
 * 上下文配置
 *
 * @author  wchen
 * @date    2019/11/11
 */
public class ApplicationConfig {
    private final static Logger LOGGER = LoggerFactory.getLogger(ApplicationConfig.class);
    private final static String CLASS_PATH_KEY = "java.class.path";

    //相关解析器
    private ParamValueProducer paramValueProducer;
    private List<TypeStructureResolver> typeStructureResolverChain = new ArrayList<>();
    private List<DocTypeAnalysis> docTypeAnalysisChain = new ArrayList<>();
    private ClassLoader defaultClassLoader;
    private DataProcessor dataProcessor;
    private ResourceLoader resourceLoader;

    //自定义常用数据
    private Map<String, String> customData = new HashMap<>();
    //自定义文档类型映射
    private Map<String, String> typeMapping = new HashMap<>();

    private Properties prop;
    private DocumentConfig documentConfig;
    private TargetProjectConfig targetProjectConfig;
    private CoreConfig coreConfig;
    private SqlConfig sqlConfig;
    //freemarker
    private Configuration freemarkerConfiguration;

    private static ApplicationConfig applicationConfig = null;

    private ApplicationConfig() {
        try {
            loadProperties();

            this.targetProjectConfig = TargetProjectConfig.initTargetAnnotationConfig(this.prop);
            this.coreConfig = CoreConfig.initCoreConfig(this.prop);
            this.documentConfig = DocumentConfig.initDocumentConfig(this.prop);
            this.initCoreProperties();
            initCustomData();
            initTypeMapping();
            checkConfig();
            LOGGER.info("ApplicationConfig init success!");
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("初始化异常 ... ");
        }
    }

    public synchronized static ApplicationConfig instance(){
        if(applicationConfig == null){
            applicationConfig = new ApplicationConfig();
        }
        return applicationConfig;
    }


    /**
     * 初始化自定义数据
     */
    private void initCustomData(){
        Properties properties = new Properties();
        try {
            InputStream inputStream = ApplicationConfig.class.getClassLoader().getResourceAsStream("custom-data.properties");
            InputStreamReader streamReader = new InputStreamReader(inputStream, "UTF-8");
            properties.load(streamReader);
            LOGGER.info("initCustomData success !");
        } catch (Exception e) {
            LOGGER.error("initCustomData error !", e);
        }

        properties.forEach((o, o2) -> this.customData.put(o.toString(), o2.toString()));
    }

    /**
     * 初始化类型映射数据
     */
    private void initTypeMapping(){
        Properties properties = new Properties();
        try {
            InputStream inputStream = ApplicationConfig.class.getClassLoader().getResourceAsStream("type-convert.properties");
            InputStreamReader streamReader = new InputStreamReader(inputStream, "UTF-8");
            properties.load(streamReader);
            LOGGER.info("initTypeMapping success !");
        } catch (Exception e) {
            LOGGER.error("initTypeMapping error !", e);
        }

        properties.forEach((o, o2) -> this.typeMapping.put(o.toString(), o2.toString()));
    }

    /**
     * 核心配置初始化
     */
    private void initCoreProperties(){
        String systemClassPath = System.getProperty(CLASS_PATH_KEY) + ";" + this.targetProjectConfig.getTargetClassPath();
        System.setProperty(CLASS_PATH_KEY, systemClassPath);
        ClassLoader defaultClassLoader = new DefaultClassLoader(this.targetProjectConfig.getTargetClassPath());
        this.defaultClassLoader = defaultClassLoader;

        try {
            String parameterProducerConfig = coreConfig.getParamValueProducer();
            if (StringUtils.isEmpty(parameterProducerConfig)) {
                this.paramValueProducer = new DefaultParamValueProducer();
            } else {
                this.paramValueProducer = (ParamValueProducer) Class.forName(parameterProducerConfig).newInstance();
            }

            //参数解析器链处理
            TypeStructureResolver baseTypeResolver = new BaseTypeStructureResolver();
            TypeStructureResolver objectTypeResolver = new ObjectStructureResolver();
            TypeStructureResolver listTypeResolver = new ListTypeStructureResolver();
            this.typeStructureResolverChain.add(baseTypeResolver);
            this.typeStructureResolverChain.add(listTypeResolver);
            List<String> paramResolverChainConfig = coreConfig.getTypeStructureResolverChain();
            if(Objects.nonNull(paramResolverChainConfig) && !paramResolverChainConfig.isEmpty()){
                for (String paramResolverPath : paramResolverChainConfig) {
                    TypeStructureResolver typeStructureResolver = (TypeStructureResolver)Class.forName(paramResolverPath).newInstance();
                    this.typeStructureResolverChain.add(typeStructureResolver);
                }
            }
            this.typeStructureResolverChain.add(new CustomTypeStructureResolver());
            this.typeStructureResolverChain.add(objectTypeResolver);

            //javaDoc 解析器链
            docTypeAnalysisChain.add(new BaseTypeDocAnalysis());
            docTypeAnalysisChain.add(new ListTypeDocAnalysis());
            List<String> docAnalysisChainConfig = coreConfig.getDocTypeAnalysisChain();
            if(Objects.nonNull(docAnalysisChainConfig) && !docAnalysisChainConfig.isEmpty()){
                for (String docAnalysisChain : docAnalysisChainConfig) {
                    DocTypeAnalysis docTypeAnalysis = (DocTypeAnalysis)Class.forName(docAnalysisChain).newInstance();
                    this.docTypeAnalysisChain.add(docTypeAnalysis);
                }
            }
            docTypeAnalysisChain.add(new CustomTypeDocAnalysis());
            docTypeAnalysisChain.add(new ObjectTypeDocAnalysis());

            //数据处理器(默认小幺鸡)
            String dataProcessorConfig = coreConfig.getDataProcessor();
            if (StringUtils.isEmpty(dataProcessorConfig)) {
                this.dataProcessor = new DefaultDataProcessor();
            } else {
                this.dataProcessor = (DataProcessor) Class.forName(dataProcessorConfig).newInstance();
            }

            //sqlConfig
            this.sqlConfig = SqlConfig.initCoreConfig(prop);

            //freemarker
            freemarkerConfiguration = new Configuration(Configuration.getVersion());
            try {
//                String path = this.getClass().getClassLoader().getResource("").toURI().getPath() +  "freemarker" ;
                File file = ResourceUtils.getFile("classpath:freemarker");
                freemarkerConfiguration.setDirectoryForTemplateLoading(file);
                // 设置config的默认字符集，一般是UTF-8
                freemarkerConfiguration.setDefaultEncoding("UTF-8");
                // 设置错误控制器
                freemarkerConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e){
            LOGGER.error("initCoreProperties error, ", e);
            throw new RuntimeException("initCoreProperties error ...");
        }
    }


    /**
     * 配置校验
     */
    private void checkConfig(){
        targetProjectConfig.validityCheck();
        coreConfig.validityCheck();
        documentConfig.validityCheck();
        sqlConfig.validityCheck();
    }


    /**
     * 加载文件
     * @return
     */
    private void loadProperties(){
        this.prop = new Properties();
        try {
            InputStream inputStream = ApplicationConfig.class.getClassLoader().getResourceAsStream("application.properties");
            InputStreamReader streamReader = new InputStreamReader(inputStream, "UTF-8");
            this.prop.load(streamReader);
            LOGGER.info("loadProperties success !");
        } catch (Exception e) {
            LOGGER.error("loadProperties error !", e);
        }
    }

    public ParamValueProducer getParamValueProducer() {
        return paramValueProducer;
    }

    public void setParamValueProducer(ParamValueProducer paramValueProducer) {
        this.paramValueProducer = paramValueProducer;
    }

    public List<TypeStructureResolver> getTypeStructureResolverChain() {
        return typeStructureResolverChain;
    }

    public void setTypeStructureResolverChain(List<TypeStructureResolver> typeStructureResolverChain) {
        this.typeStructureResolverChain = typeStructureResolverChain;
    }

    public ClassLoader getDefaultClassLoader() {
        return defaultClassLoader;
    }

    public void setDefaultClassLoader(ClassLoader defaultClassLoader) {
        this.defaultClassLoader = defaultClassLoader;
    }

    public Properties getProp() {
        return prop;
    }

    public void setProp(Properties prop) {
        this.prop = prop;
    }

    public TargetProjectConfig getTargetProjectConfig() {
        return targetProjectConfig;
    }

    public void setTargetProjectConfig(TargetProjectConfig targetProjectConfig) {
        this.targetProjectConfig = targetProjectConfig;
    }

    public CoreConfig getCoreConfig() {
        return coreConfig;
    }

    public void setCoreConfig(CoreConfig coreConfig) {
        this.coreConfig = coreConfig;
    }

    public DataProcessor getDataProcessor() {
        return dataProcessor;
    }

    public void setDataProcessor(DataProcessor dataProcessor) {
        this.dataProcessor = dataProcessor;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    public List<DocTypeAnalysis> getDocTypeAnalysisChain() {
        return docTypeAnalysisChain;
    }

    public void setDocTypeAnalysisChain(List<DocTypeAnalysis> docTypeAnalysisChain) {
        this.docTypeAnalysisChain = docTypeAnalysisChain;
    }

    public Map<String, String> getCustomData() {
        return customData;
    }

    public void setCustomData(Map<String, String> customData) {
        this.customData = customData;
    }

    public Configuration getFreemarkerConfiguration() {
        return freemarkerConfiguration;
    }

    public void setFreemarkerConfiguration(Configuration freemarkerConfiguration) {
        this.freemarkerConfiguration = freemarkerConfiguration;
    }

    public SqlConfig getSqlConfig() {
        return sqlConfig;
    }

    public void setSqlConfig(SqlConfig sqlConfig) {
        this.sqlConfig = sqlConfig;
    }

    public Map<String, String> getTypeMapping() {
        return typeMapping;
    }

    public void setTypeMapping(Map<String, String> typeMapping) {
        this.typeMapping = typeMapping;
    }

    public DocumentConfig getDocumentConfig() {
        return documentConfig;
    }

    public void setDocumentConfig(DocumentConfig documentConfig) {
        this.documentConfig = documentConfig;
    }
}
